package com.gitee.ice1938.module.admin.framwork;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

/**
 * 自定义错误页面处理(由于ErrorMvcAutoConfiguration 中使用DefaultErrorViewResolver
 * 类型进行识别，所有必须继承DefaultErrorViewResolver)
 * 
 * @author sunyan ice1938@qq.com
 * @see DefaultErrorViewResolver
 */
public class AdminErrorViewResolver extends DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

	private static final Map<Series, String> SERIES_VIEWS;

	static {
		Map<Series, String> views = new EnumMap<>(Series.class);
		views.put(Series.CLIENT_ERROR, "4xx");
		views.put(Series.SERVER_ERROR, "5xx");
		SERIES_VIEWS = Collections.unmodifiableMap(views);
	}

	private ApplicationContext applicationContext;

	private final ResourceProperties resourceProperties;

	private final TemplateAvailabilityProviders templateAvailabilityProviders;

	private int order = Ordered.LOWEST_PRECEDENCE;

	/**
	 * Create a new {@link DefaultErrorViewResolver} instance.
	 * 
	 * @param applicationContext
	 *            the source application context
	 * @param resourceProperties
	 *            resource properties
	 */
	public AdminErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
		this(applicationContext, resourceProperties, new TemplateAvailabilityProviders(applicationContext));
	}

	public AdminErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties,
	        TemplateAvailabilityProviders templateAvailabilityProviders) {
		super(applicationContext, resourceProperties);
		Assert.notNull(applicationContext, "ApplicationContext must not be null");
		Assert.notNull(resourceProperties, "ResourceProperties must not be null");
		this.applicationContext = applicationContext;
		this.resourceProperties = resourceProperties;
		this.templateAvailabilityProviders = templateAvailabilityProviders;
	}

	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {

		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
		List<String> paths = new ArrayList<String>();
		// 添加自定义错误模版页面--根据请求路径进行映射，如果请求路径下没有，按照按照默认规则进行查找(classpath:templates/error))
		String reqBasePath = (String) model.get("path");
		if (reqBasePath.lastIndexOf("/") != reqBasePath.indexOf("/")) {
			int beginIndex = 0;
			if (reqBasePath.startsWith("/")) {
				beginIndex = 1;
			}
			reqBasePath = reqBasePath.substring(beginIndex, reqBasePath.lastIndexOf("/"));
			paths.add(reqBasePath + "/error");
		}
		paths.add("error");
		// 首选模版路径
		for (String path : paths) {
			String errorViewName = path + "/" + viewName;
			TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
			        this.applicationContext);
			if (provider != null) {
				return new ModelAndView(errorViewName, model);
			}
		}
		// 解析经停模版页面
		for (String path : paths) {
			String errorViewName = path + "/" + viewName;
			ModelAndView view = resolveResource(errorViewName, model);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

	/**
	 * 判断页面知否真实存在
	 * 
	 * @param viewName
	 * @param model
	 * @return
	 */
	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resourceProperties.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			} catch (Exception ex) {
			}
		}
		return null;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

	@Override
    public void setOrder(int order) {
		this.order = order;
	}

	/**
	 * {@link View} backed by an HTML resource.
	 */
	private static class HtmlResourceView implements View {

		private Resource resource;

		HtmlResourceView(Resource resource) {
			this.resource = resource;
		}

		@Override
		public String getContentType() {
			return MediaType.TEXT_HTML_VALUE;
		}

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
		        throws Exception {
			response.setContentType(getContentType());
			FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
		}

	}

}
